package com.ingenico.tools.data.sampling;

import java.awt.geom.Path2D;
import java.io.IOException;
import java.util.logging.Logger;

import com.ingenico.tools.nio.SampleBuffer;
import com.ingenico.tools.nio.channel.RandomReadableSampleChannel;

public abstract class AbstractDiscretePathSampler implements PathSampler {
	private static final Logger _logger = Logger.getLogger(AbstractDiscretePathSampler.class.getCanonicalName());

	private SampleBuffer samplesBuffer = null;

	protected abstract double getDoubleValue (SampleBuffer buffer);

	protected Path2D subsampleToPath (final RandomReadableSampleChannel samplesChannel, double targetScale, int targetLength, long sampleOffset) throws IOException {
		// Now, we create the path and - if required - the samples' buffer
		final Path2D path = new Path2D.Double();
		final double virtualSamplesWindowLength = 1 / targetScale;

		double virtualX;
		long realX;
		double y;

		int requiredSampleWindowLength = (int)virtualSamplesWindowLength;
		int availableSamples;

		// We create the sample buffer if required and we set its size
		if ( (samplesBuffer == null) || (samplesBuffer.capacity() < requiredSampleWindowLength) ) {
			samplesBuffer = samplesChannel.allocate(requiredSampleWindowLength);
			_logger.fine("SamplesBuffer capacity set to " + requiredSampleWindowLength + " samples");
		}

		// We position the samples channel to the first real sample
		virtualX = sampleOffset;
		realX = sampleOffset;
		samplesChannel.position(realX);
		path.reset();

		_logger.fine("realX = " + realX + ", virtualX = " + virtualX);
		_logger.fine("requiredSampleWindowLength = " + requiredSampleWindowLength + ", virtualSamplesWindowLength = " + virtualSamplesWindowLength);

		samplesBuffer.clear();
		samplesBuffer.limit(requiredSampleWindowLength);
		availableSamples = samplesChannel.read(samplesBuffer);
		_logger.fine("read " + availableSamples + " samples");

		// The sample buffer will be read back so we set its position and limit.
		samplesBuffer.position(0);
		if (availableSamples > 0) {
			samplesBuffer.limit(availableSamples);

			// Get representative value and sets the initial point.
			y = getDoubleValue(samplesBuffer);
//			_logger.finer("MoveTo " + realX + ", " + y);
			path.moveTo(realX, y);
		} else {
			// When we reach the end of stream, we cannot read any further.
			// Let's return what we got
			return path;
		}

		// main chunk loop (lineTo() the other points)
		for (int i=1; i < targetLength; i++) {
			virtualX += virtualSamplesWindowLength;
			realX += availableSamples;
			requiredSampleWindowLength = (int)(virtualX - realX) + (int)virtualSamplesWindowLength;

//			_logger.fine("__ realX = " + realX + ", virtualX = " + virtualX);
//			_logger.fine("__ requiredSampleWindowLength = " + requiredSampleWindowLength + ", virtualSamplesWindowLength = " + virtualSamplesWindowLength);

			// At this stage, the sample buffer exists, we just adjust its size if required
			if (samplesBuffer.capacity() < requiredSampleWindowLength) {
				samplesBuffer = samplesChannel.allocate(requiredSampleWindowLength);
//				_logger.fine("__ SamplesBuffer capacity set to " + requiredSampleWindowLength + " samples");
			}
			samplesBuffer.clear();
			samplesBuffer.limit(requiredSampleWindowLength);

			// Jump to the first sample of the next window
			samplesChannel.position(realX);
			availableSamples = samplesChannel.read(samplesBuffer);
//			_logger.fine("__ read " + availableSamples + " samples");

			// The sample buffer will be read back so we set its position and limit.
			samplesBuffer.position(0);
			if (availableSamples > 0) {
				samplesBuffer.limit(availableSamples);

				// Get representative value and connect to the last point.
				y = getDoubleValue(samplesBuffer);
				path.lineTo(realX, y);
//				_logger.finer("LineTo " + realX + ", " + y);
			} else {
				// When we reach the end of stream, we cannot read any further.
				// Let's return what we got
				return path;
			}
		}

		return path;
	}
	
	protected Path2D supersampleToPath (final RandomReadableSampleChannel samplesChannel, double targetScale, int targetLength, long sampleOffset) throws IOException {
		final Path2D path = new Path2D.Double();
		int realLength = (int)((double)targetLength/targetScale);

		long realX;
		double y;

		final int requiredSampleWindowLength = 1;
		int availableSamples;

		// We create the sample buffer if required and we set its size
		if ( (samplesBuffer == null) || (samplesBuffer.capacity() < requiredSampleWindowLength) ) {
			samplesBuffer = samplesChannel.allocate(requiredSampleWindowLength);
			_logger.fine("SamplesBuffer capacity set to " + requiredSampleWindowLength + " samples");
		}
		
		// We position the samples channel to the first real sample
		realX = sampleOffset;
		samplesChannel.position(realX);
		path.reset();

		samplesBuffer.clear();
		samplesBuffer.limit(requiredSampleWindowLength);
		availableSamples = samplesChannel.read(samplesBuffer);
		_logger.fine("read " + availableSamples + " samples");

		// The sample buffer will be read back so we set its position and limit.
		samplesBuffer.position(0);
		if (availableSamples > 0) {
			samplesBuffer.limit(availableSamples);

			// Get representative value and sets the initial point.
			y = getDoubleValue(samplesBuffer);
//			_logger.finer("MoveTo " + realX + ", " + y);
			path.moveTo(realX, y);
		} else {
			// When we reach the end of stream, we cannot read any further.
			// Let's return what we got
			return path;
		}

		// main chunk loop (lineTo() the other points)
		for (int i=1; i < realLength; i++) {
			realX += availableSamples;

//			_logger.fine("__ realX = " + realX + ", virtualX = " + virtualX);
//			_logger.fine("__ requiredSampleWindowLength = " + requiredSampleWindowLength + ", virtualSamplesWindowLength = " + virtualSamplesWindowLength);

			samplesBuffer.clear();
			samplesBuffer.limit(requiredSampleWindowLength);

			// Jump to the first sample of the next window
			samplesChannel.position(realX);
			availableSamples = samplesChannel.read(samplesBuffer);
//			_logger.fine("__ read " + availableSamples + " samples");

			// The sample buffer will be read back so we set its position and limit.
			samplesBuffer.position(0);
			if (availableSamples > 0) {
				samplesBuffer.limit(availableSamples);

				// Get representative value and connect to the last point.
				y = getDoubleValue(samplesBuffer);
				path.lineTo(realX, y);
//				_logger.finer("LineTo " + realX + ", " + y);
			} else {
				// When we reach the end of stream, we cannot read any further.
				// Let's return what we got
				return path;
			}
		}

		return path;
	}

	@Override
	public Path2D sampleToPath(final RandomReadableSampleChannel samplesChannel, double targetScale, int targetLength, long sampleOffset) throws IOException {
		final long samplesChannelSize = samplesChannel.size();
_logger.fine("targetLength = " + targetLength);
		/*
		 *  No need to do anything if
		 *  - the target length is null
		 *  -  the offset is beyond the byte channel's capacity
		 */
		if ( (targetLength == 0) || (samplesChannelSize <= sampleOffset) ) {
			return null;
		}

		// No need to actually re-sample anything if the target is not visible 
		if (targetLength < 1) {
			return null;
		}

		if (targetScale < 1) {
			return subsampleToPath(samplesChannel, targetScale, targetLength, sampleOffset);
		} else {
			return supersampleToPath(samplesChannel, targetScale, targetLength, sampleOffset);
		}
	}

	@Override
	public String toString() {
		return getClass().getSimpleName();
	}

}
